package org.dant.ant.extension.tasks;

import java.io.IOException;
import java.util.*;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.FileUtils;
import org.dant.ant.extension.tasks.commands.PingCommand;
import org.dant.ant.extension.tasks.internal.Endpoint;
import org.dant.ant.extension.tasks.internal.RemoteCommand;
import org.dant.ant.extension.tasks.internal.RemoteResponse;
import org.dant.ant.extension.types.Host;
import org.dant.ant.extension.types.HostSet;
import org.dant.ant.extension.utils.ParameterUtil;

public class RemoteTask extends Task {

    protected boolean failOnError;
    protected Long timeout;
  
    protected Vector hosts = new Vector();
    protected Vector hostsets = new Vector();

    protected RemoteCommand rcmd;
    protected RemoteResponse response;

    protected boolean transmit = true;
  

    protected List resultsList;

    protected TestCase testcase;
    protected Map destHosts = new HashMap();
    protected FileUtils fu = FileUtils.newFileUtils();

    private boolean initializedHost;
    private List cmdSenderList = new ArrayList();
    
    public RemoteTask() {
        resultsList = new ArrayList();
    }

    public void setTransmit(boolean t) {
        this.transmit = t;
    }

    public void setOwningTestCase(TestCase t) {
        this.testcase = t;
    }

    public TestCase getOwningTestCase() {
        if (testcase == null) {
            setOwningTestCase(new TestCase());
        }
        return this.testcase;
    }

    public String getTestSuiteName() {
        return getOwningTestCase().getOwningTestSuite().getName();
    }
  
    public String getTestCaseName() {
        return getOwningTestCase().getName();
    }

    public String getProjectName() {
        return getProject().getName();
    }

    public String getTargetName() {
        return getOwningTarget().getName();
    }

    public boolean getTransmit() {
        return this.transmit;
    }

    public void setHostRef(Reference r) {
        Object o = r.getReferencedObject(getProject());
        if (!(o instanceof Host)) {
            String msg = r.getRefId() + " doesn\'t denote a host";
            throw new BuildException(msg);
        }
        hosts.addElement((Host)o);
    }

    public void setHostSetRef(Reference r) {
        Object o = r.getReferencedObject(getProject());
        if (!(o instanceof HostSet)) {
            String msg = r.getRefId() + " doesn\'t denote a hostset";
            throw new BuildException(msg);
        }
    
        hostsets.addElement((HostSet)o);
    }
  
    public void addHost(Host host) {
        this.hosts.addElement(host);
    }
  
    public void addHostset(HostSet hostset) {
        this.hostsets.addElement(hostset);
    }

    public void setFailonerror(boolean f) {
        this.failOnError = f;
    }
  
    public boolean getFailOnError() {
        return this.failOnError;
    }

    public void setTimeout(Long t) {
        this.timeout = t;
    }

    public Long getTimeOut() {
        return this.timeout;
    }

    private void sendCMD2Host(Endpoint endpoint, RemoteCommand rcommand, boolean t) {
        CommandSender cmdSender = new CommandSender();
        cmdSender.setEndpoint(endpoint);
        cmdSender.setTransmit(t);
        cmdSender.setRemoteCommand(rcommand);
        cmdSender.start();
        cmdSenderList.add(cmdSender);
    }

    private boolean isServerAlive(Endpoint destHost) {
        PingCommand pc  = new PingCommand();
        pc.setEcho("alive");
        pc.setSourceHost(Endpoint.getLocalhost());
        try {
            pc.init();
            RemoteResponse r = destHost.getRemoteHelper().invoke(destHost, pc);
            return r.isSuccess() && "alive".equals((String)r.getResult().trim());
        } catch (Exception e) {
            return false;
        }
    }

    private Endpoint resolveHost(Host host) {
        Endpoint destHost = new Endpoint(host.getHostName(),
                                         host.getIp(),
                                         host.getPort(),
                                         host.getNetworking(),
                                         host.getBaseDir());
    
        if (destHost.getRemoteHelper() == null) {
            if (failOnError) {
                throw new BuildException("Can't initialize the networking class " + host.getNetworking());
            } else {
                return null;
            }
        }

        destHost.setFullURL(destHost.getHostEndpoint());

        if (!transmit) {
            return destHost;
        }
    
        log("Trying the endpoint: " + destHost.getHostEndpoint() + " ...");
        if (!isServerAlive(destHost)) {
            log("       - endpoint: " + destHost.getHostEndpoint() + " failed!");
            destHost.setFullURL(destHost.getIPEndpoint());
            log("Trying the endpoint: " + destHost.getIPEndpoint() + " ...");
            if (!isServerAlive(destHost)) {
                log("       - endpoint: " + destHost.getIPEndpoint() + " failed!");
                if (failOnError) {
                    throw new BuildException("Hostname: " + host.getHostName() + " or IP: "
                                             + host.getIp() + " with port:" + host.getPort() + " is not availalbe");
                } else {
                    return null;
                }
            }
        }
        return destHost;
    }

    private void validate() {
        if ((hosts.size() + hostsets.size()) == 0) {
            throw new BuildException("No <host> or <hostset> specified in the the remote task");
        }
    }

    private void initialCommand() throws Exception {
        rcmd.appendParameters(ParameterUtil.resolve(this, false));
        rcmd.setSourceHost(Endpoint.getLocalhost());
        rcmd.init();
    }

    private String failToStart(int totalProcess, int hwm) {
        StringBuffer exp = new StringBuffer();
        exp.append("You want to start ");
        exp.append(totalProcess);
        exp.append(" processes which is over the high water mark ");
        exp.append(hwm);
        exp.append(" you defined in the [hostset], the default value for each [host] is 1");
        return exp.toString();
    }
  
    private void initialHosts() throws Exception {

        if (initializedHost) {
            return;
        } else {
            initializedHost = true;
        }
    
        if (hosts.size() > 0) {
            int totalProcess = 0;
            for (int i = 0; i < hosts.size(); i++) {
                Host host = (Host) hosts.elementAt(i);
                if (this instanceof RemoteExecutable) {
                    totalProcess = ((Integer)rcmd.getParameters().get("duplicate")).intValue();
                    if (totalProcess > host.getHighWaterMark()) {
                        throw new BuildException(failToStart(totalProcess, host.getHighWaterMark()).toString());
                    }
                }
                Endpoint ep = resolveHost(host);
                if (ep != null) {
                    destHosts.put(ep, rcmd);
                }
                    
            }
        }

        if (hostsets.size() > 0) {
            int hwm = getHighWaterMark();
            boolean share = false;
            int totalProcess = 0;

            if (this instanceof RemoteExecutable) {
                share = ((Boolean)rcmd.getParameters().get("share")).booleanValue();
                totalProcess = ((Integer)rcmd.getParameters().get("duplicate")).intValue();
                if (share && hwm < totalProcess) {
                    throw new BuildException(failToStart(totalProcess, hwm).toString());
                }
            }
      
            for (int i = 0; i < hostsets.size(); i++) {
                HostSet hostset = (HostSet) hostsets.elementAt(i);
        
                for (Iterator iter = hostset.list().iterator(); iter.hasNext();) {
                    Host host = (Host) iter.next();
                    Endpoint ep = resolveHost(host);
                    if (ep == null) {
                        continue;
                    }

                    if (share) {
                        if (totalProcess <= 0) {
                            return;
                        }
                        Integer dupli;
                        if (host.getHighWaterMark() < totalProcess) {
                            dupli = new Integer(host.getHighWaterMark());
                        } else {
                            dupli = new Integer(totalProcess);
                        }
            
                        RemoteCommand request = (RemoteCommand) rcmd.clone();
                        request.appendParameters("duplicate", dupli);
                        destHosts.put(ep, request);
                        totalProcess -= dupli.intValue();
                    } else {
                        destHosts.put(ep, rcmd);
                    }
                }
            }
        }
    }

    private int getHighWaterMark() {
        int cumulation = 0;
        if (hostsets.size() > 0) {
            for (int i = 0; i < hostsets.size(); i++) {
                cumulation += ((HostSet)hostsets.elementAt(i)).getHighWaterMark();
            }
        }
        return cumulation;
    }

    public void resolveDir() throws IOException {
    }

    public void initialize() throws Exception {
        initialCommand();
        initialHosts();
        resolveDir();
    }

    public void execute() throws BuildException {
        try {
            initialize();
        } catch (Exception e) {
            e.printStackTrace();
        }

        validate();

        if (destHosts.size() == 0) {
            Host localhost = new Host();
            localhost.setNetworking("org.dant.ant.extension.net.LocalHelper");
            localhost.setHostName("localhost");
            localhost.setIp("127.0.0.1");
            localhost.setPort("1979");
            sendCMD2Host(resolveHost(localhost), rcmd, transmit);
        } else {
            for (Iterator iter = destHosts.keySet().iterator(); iter.hasNext();) {
                Endpoint ep = (Endpoint) iter.next();
                sendCMD2Host(ep, (RemoteCommand)destHosts.get(ep), transmit);
            }
        }

        for (Iterator iter = cmdSenderList.iterator(); iter.hasNext();) {
            CommandSender cmdSender = (CommandSender) iter.next();
            try {
                cmdSender.getThread().join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (Iterator iter = cmdSenderList.iterator(); iter.hasNext();) {
            CommandSender cmdSender = (CommandSender) iter.next();
            addResult(cmdSender.getResponse());
            handleResults(cmdSender.getResponse());
        }
    }

    protected void handleResults(RemoteResponse r) {
        if (r != null) {
            log(r.toString());
        } else {
            if (failOnError) {
                throw new BuildException("No response returned!");
            }
        }
      
        if (failOnError) {
            if (!r.isSuccess()) {
                throw new BuildException("Detected fail in this build : " + r.getResult());
            }
        }
    }

    public List getResultsList() {
        return this.resultsList;
    }
  
    public void addResult(RemoteResponse r) {
        this.resultsList.add(r);
    }
}
